package vapu.examples.dreinulldrei;

import im.composer.vapu.VAPU;

import javax.sound.midi.MidiMessage;

public class DreiNullDrei extends VAPU {
	private static final float ENV_INC = 64;

	private static final int NUM_PROGRAMS = 16;

	private DreiNullDreiProgram[] programs = new DreiNullDreiProgram[NUM_PROGRAMS];
	private int channelPrograms[] = new int[NUM_PROGRAMS];
	private int currentProgram;

	private boolean IsNoteOn = false;
	private int currentNote;

	private float srate;

	private float vco_inc_src, vco_inc, vco_inc_dest, vco_k, vco_wav;

	private float vcf_envmod, vcf_envdecay, vcf_envdecayi, vcf_reso, vcf_rescoeff, vcf_cutoff, vcf_e0, vcf_e1, vcf_c0, vcf_d1, vcf_d2, vcf_a, vcf_b, vcf_c, vcf_envpos;

	private float vca_attack, vca_decay, vca_a0, vca_a, vca_mode, vca_accamt;

	private float sld_spd;
	private int cnt, cntmax;

	private float volume;

	public DreiNullDrei() {

		for (int i = 0; i < this.programs.length; i++)
			this.programs[i] = new DreiNullDreiProgram();
		for (int i = 0; i < this.channelPrograms.length; i++)
			this.channelPrograms[i] = i;

		this.setProgram(0);

		this.srate = 44100f;
		this.vco_inc_dest = 440f / this.srate;
		this.vco_inc = this.vco_inc_dest;
		this.vco_wav = 0f;
		this.vco_k = this.vcf_cutoff = this.vcf_envmod = this.vcf_reso = 0f;
		this.vco_inc_dest = this.vcf_envdecay = 0f;
		this.vcf_envpos = ENV_INC;

		this.vcf_a = this.vcf_b = this.vcf_d1 = this.vcf_d2 = this.vcf_c0 = this.vcf_e0 = this.vcf_e1 = 0;
		this.vca_mode = 2;
		this.vca_a = 0f;
		this.vcf_rescoeff = 1f;
		this.vca_attack = 1f - 0.94406088f;
		this.vca_decay = 0.99897516f;
		this.vca_a0 = 0.5f;
		this.vca_accamt = 0.5f;
		this.sld_spd = 0.1f;
		this.setCutoff(0.9f);
		this.setResonance(0.1f);
		this.setEnvMod(1f);
		this.setEnvDecay(0.1f);
		this.cntmax = 1;
		this.volume = 1f;

	}

	public void setSampleRate(float s) {
		this.srate = s;
	}

	public void setProgram(int index) {
		if (index < 0 || index >= NUM_PROGRAMS)
			return;

		DreiNullDreiProgram dp = this.programs[index];
		this.currentProgram = index;

		this.setParameter(DreiNullDreiProgram.PARAM_ID_VOLUME, dp.getVolume());

		this.setParameter(DreiNullDreiProgram.PARAM_ID_ACC_AMOUNT, dp.getAccAmount());
		this.setParameter(DreiNullDreiProgram.PARAM_ID_CUT_OFF, dp.getCutoff());
		this.setParameter(DreiNullDreiProgram.PARAM_ID_ENV_DECAY, dp.getEnvDecay());
		this.setParameter(DreiNullDreiProgram.PARAM_ID_ENV_MOD, dp.getEnvMod());
		this.setParameter(DreiNullDreiProgram.PARAM_ID_GLIDE_SPEED, dp.getGlideSpeed());
		this.setParameter(DreiNullDreiProgram.PARAM_ID_RESONANCE, dp.getResonance());
		this.setParameter(DreiNullDreiProgram.PARAM_ID_WAVEFORM, dp.getWaveform());
	}

	public void setProgramName(String name) {
		this.programs[this.currentProgram].setName(name);
	}

	public String getProgramName() {
		String name;

		if (this.programs[this.currentProgram].getName().equals("Init")) {
			name = this.programs[this.currentProgram].getName() + " " + (this.currentProgram + 1);
		} else {
			name = this.programs[this.currentProgram].getName();
		}

		return name;
	}

	public String getParameterLabel(int index) {
		String label = "";

		switch (index) {
		case DreiNullDreiProgram.PARAM_ID_WAVEFORM:
			label = "Shape";
			break;
		case DreiNullDreiProgram.PARAM_ID_ACC_AMOUNT:
		case DreiNullDreiProgram.PARAM_ID_CUT_OFF:
		case DreiNullDreiProgram.PARAM_ID_ENV_DECAY:
		case DreiNullDreiProgram.PARAM_ID_ENV_MOD:
		case DreiNullDreiProgram.PARAM_ID_GLIDE_SPEED:
		case DreiNullDreiProgram.PARAM_ID_RESONANCE:
			label = "";
			break;
		case DreiNullDreiProgram.PARAM_ID_VOLUME:
			label = "dB";
			break;
		}

		return label;
	}

	public String getParameterName(int index) {
		String label = "";

		switch (index) {
		case DreiNullDreiProgram.PARAM_ID_ACC_AMOUNT:
			label = "Acc Amount";
			break;
		case DreiNullDreiProgram.PARAM_ID_CUT_OFF:
			label = "Cutoff";
			break;
		case DreiNullDreiProgram.PARAM_ID_ENV_DECAY:
			label = "Env Decay";
			break;
		case DreiNullDreiProgram.PARAM_ID_ENV_MOD:
			label = "Env Mod";
			break;
		case DreiNullDreiProgram.PARAM_ID_GLIDE_SPEED:
			label = "Glide Speed";
			break;
		case DreiNullDreiProgram.PARAM_ID_RESONANCE:
			label = "Resonance";
			break;
		case DreiNullDreiProgram.PARAM_ID_VOLUME:
			label = "Volume";
			break;
		case DreiNullDreiProgram.PARAM_ID_WAVEFORM:
			label = "Waveform";
			break;

		}

		return label;
	}

	public void setParameter(int index, float value) {
		DreiNullDreiProgram dp = this.programs[this.currentProgram];

		switch (index) {
		case DreiNullDreiProgram.PARAM_ID_ACC_AMOUNT: {
			dp.setAccAmount(value);
			this.setAccAmt(value);
			break;
		}
		case DreiNullDreiProgram.PARAM_ID_CUT_OFF: {
			dp.setCutoff(value);
			this.setCutoff(value);
			break;
		}
		case DreiNullDreiProgram.PARAM_ID_ENV_DECAY: {
			dp.setEnvDecay(value);
			this.setEnvDecay(value);
			break;
		}
		case DreiNullDreiProgram.PARAM_ID_ENV_MOD: {
			dp.setEnvMod(value);
			this.setEnvMod(value);
			break;
		}
		case DreiNullDreiProgram.PARAM_ID_GLIDE_SPEED: {
			dp.setGlideSpeed(value);
			this.setGlideSpeed(value);
			break;
		}
		case DreiNullDreiProgram.PARAM_ID_RESONANCE: {
			dp.setResonance(value);
			this.setResonance(value);
			break;
		}
		case DreiNullDreiProgram.PARAM_ID_VOLUME: {
			dp.setVolume(value);
			this.volume = value;
			break;
		}
		case DreiNullDreiProgram.PARAM_ID_WAVEFORM: {
			dp.setWaveform(value);
			this.setWaveform(value);
			break;
		}
		}

	}

	public float getParameter(int index) {
		float v = 0;

		switch (index) {
		case DreiNullDreiProgram.PARAM_ID_ACC_AMOUNT:
			v = this.getAccAmt();
			break;
		case DreiNullDreiProgram.PARAM_ID_CUT_OFF:
			v = this.getCutoff();
			break;
		case DreiNullDreiProgram.PARAM_ID_ENV_DECAY:
			v = this.getEnvDecay();
			break;
		case DreiNullDreiProgram.PARAM_ID_ENV_MOD:
			v = this.getEnvMod();
			break;
		case DreiNullDreiProgram.PARAM_ID_GLIDE_SPEED:
			v = this.getGlideSpeed();
			break;
		case DreiNullDreiProgram.PARAM_ID_RESONANCE:
			v = this.getResonance();
			break;
		case DreiNullDreiProgram.PARAM_ID_VOLUME:
			v = this.volume;
			break;
		case DreiNullDreiProgram.PARAM_ID_WAVEFORM:
			v = this.getWaveform();
			break;
		}
		return v;
	}

	public String getProgramNameIndexed(int category, int index) {
		String text = "";
		if (index < this.programs.length)
			text = this.programs[index].getName();
		if ("Init".equals(text))
			text = text + " " + index;
		return text;
	}

	public boolean copyProgram(int destination) {
		if (destination < NUM_PROGRAMS) {
			this.programs[destination] = this.programs[this.currentProgram];
			return true;
		}
		return false;
	}

	public String getEffectName() {
		return "DreiNullDrei v0.6";
	}

	public String getVendorString() {
		return "jVSTwRapper";
	}

	public String getProductString() {
		return "DreiNullDrei";
	}

	public int getNumPrograms() {
		return NUM_PROGRAMS;
	}

	public int getNumParams() {
		return DreiNullDreiProgram.NUM_PARAMS;
	}

	public boolean setBypass(boolean value) {
		return false;
	}

	public int getProgram() {
		return this.currentProgram;
	}

	public boolean string2Parameter(int index, String value) {
		boolean ret = false;

		try {
			if (value != null)
				this.setParameter(index, Float.parseFloat(value));
			ret = true;
		} catch (Exception e) {
		}

		return ret;
	}

	public boolean hasMidiProgramsChanged(int channel) {
		return false;
	}

	// DEPRECATED since 2.4
	public void process(float[][] input, float[][] output, int samples) {
		float w, k, result;
		float[] out1 = output[0];

		if (this.IsNoteOn) {
			for (int j = 0; j < samples; j++) {

				if (this.vcf_envpos >= ENV_INC) {
					w = this.vcf_e0 + this.vcf_c0;
					k = (float) Math.exp(-w / this.vcf_rescoeff);
					this.vcf_c0 = this.vcf_c0 * this.vcf_envdecay;
					this.vcf_a = 2f * (float) Math.cos(2f * w) * k;
					this.vcf_b = -k * k;
					this.vcf_c = 1f - this.vcf_a - this.vcf_b;
					this.vcf_envpos = 0;
				}

				if (this.vco_wav > 0.5f) {
					result = this.vcf_a * this.vcf_d1 + this.vcf_b * this.vcf_d2 + this.vcf_c * this.rct(this.vco_k) * this.vca_a;
				} else {
					result = this.vcf_a * this.vcf_d1 + this.vcf_b * this.vcf_d2 + this.vcf_c * this.vco_k * this.vca_a;
				}

				this.vcf_d2 = this.vcf_d1;
				this.vcf_envpos = this.vcf_envpos + 1;
				this.vcf_d1 = result;

				this.cnt++;
				w = (float) this.cnt / (float) this.cntmax;

				if (w < 1f) {
					k = this.vco_inc_src * (1f - w) + w * this.vco_inc_dest;
				} else {
					this.vco_inc = this.vco_inc_dest;
					k = this.vco_inc;
				}

				this.vco_k = this.vco_k + k;
				if (this.vco_k > 0.5f)
					this.vco_k -= 1;

				if (this.vca_mode == 0) {
					this.vca_a = this.vca_a + ((this.vca_a0 - this.vca_a) * this.vca_attack);
				} else if (this.vca_mode == 1) {
					this.vca_a = this.vca_a * this.vca_decay;

					if (this.vca_a < (1f / 65536f)) {
						this.vca_a = 0;
						this.vca_mode = 2;
					}
				}

				out1[j] += result * this.volume;
			}
		}

	}

	public void processReplacing(float[][] input, float[][] output, int samples) {
		float w, k, result;
		float[] out1 = output[0];

		if (this.IsNoteOn) {
			for (int j = 0; j < samples; j++) {

				if (this.vcf_envpos >= ENV_INC) {
					w = this.vcf_e0 + this.vcf_c0;
					k = (float) Math.exp(-w / this.vcf_rescoeff);
					this.vcf_c0 = this.vcf_c0 * this.vcf_envdecay;
					this.vcf_a = 2f * (float) Math.cos(2f * w) * k;
					this.vcf_b = -k * k;
					this.vcf_c = 1f - this.vcf_a - this.vcf_b;
					this.vcf_envpos = 0;
				}

				if (this.vco_wav > 0.5f) {
					result = this.vcf_a * this.vcf_d1 + this.vcf_b * this.vcf_d2 + this.vcf_c * this.rct(this.vco_k) * this.vca_a;
				} else {
					result = this.vcf_a * this.vcf_d1 + this.vcf_b * this.vcf_d2 + this.vcf_c * this.vco_k * this.vca_a;
				}

				this.vcf_d2 = this.vcf_d1;
				this.vcf_envpos = this.vcf_envpos + 1;
				this.vcf_d1 = result;

				this.cnt++;
				w = (float) this.cnt / (float) this.cntmax;

				if (w < 1f) {
					k = this.vco_inc_src * (1f - w) + w * this.vco_inc_dest;
				} else {
					this.vco_inc = this.vco_inc_dest;
					k = this.vco_inc;
				}

				this.vco_k = this.vco_k + k;
				if (this.vco_k > 0.5f)
					this.vco_k -= 1;

				if (this.vca_mode == 0) {
					this.vca_a = this.vca_a + ((this.vca_a0 - this.vca_a) * this.vca_attack);
				} else if (this.vca_mode == 1) {
					this.vca_a = this.vca_a * this.vca_decay;

					if (this.vca_a < (1f / 65536f)) {
						this.vca_a = 0;
						this.vca_mode = 2;
					}
				}

				out1[j] = result * this.volume;
			}
		}

	}

	public void send(MidiMessage msg,long pos) {
		byte[] midiData = msg.getMessage();
		int status = midiData[0] & 0xf0;// ignoring channel

		if (status == 0x90 || status == 0x80) {
			// we only look at notes
			int note = midiData[1] & 0x7f;
			int velocity = midiData[2] & 0x7f;
			if (status == 0x80)
				velocity = 0; // note off by velocity 0

			if (velocity == 0 && (note == currentNote))
				this.noteOff();
			else
				this.noteOn(note, velocity >= 64, false);
		} else if (status == 0xb0) {
			// all notes off
			if (midiData[1] == 0x7e || midiData[1] == 0x7b)
				this.noteOff();
		}
	}

	private void setWaveform(float w) {
		this.vco_wav = w;
	}

	private float getWaveform() {
		return this.vco_wav;
	}

	private void setGlideSpeed(float s) {
		this.sld_spd = s;
	}

	private float getGlideSpeed() {
		return this.sld_spd;
	}

	private void setAccAmt(float a) {
		this.vca_accamt = a;
	}

	private float getAccAmt() {
		return this.vca_accamt;
	}

	private void setCutoff(float c) {
		this.vcf_cutoff = c;
		this.recalc();
	}

	private float getCutoff() {
		return this.vcf_cutoff;
	}

	private void setResonance(float r) {
		this.vcf_reso = r;
		this.vcf_rescoeff = (float) Math.exp(-1.20f + 3.455f * this.vcf_reso);
		this.recalc();
	}

	private float getResonance() {
		return this.vcf_reso;
	};

	private void setEnvMod(float e) {
		this.vcf_envmod = e;
		this.recalc();
	}

	private float getEnvMod() {
		return this.vcf_envmod;
	}

	private void setEnvDecay(float d) {
		this.vcf_envdecayi = d;
		this.vcf_envdecay = (0.2f + (2.3f * vcf_envdecayi)) * this.srate;

		if (this.vcf_envdecay < 1)
			this.vcf_envdecay = 1;

		this.vcf_envdecay = (float) Math.pow(0.1f, 1f / (this.vcf_envdecay * ENV_INC));
	}

	private float getEnvDecay() {
		return this.vcf_envdecayi;
	}

	private void recalc() {
		this.vcf_e1 = (float) Math.exp(6.109f + 1.5876f * this.vcf_envmod + 2.1553f * this.vcf_cutoff - 1.2f * (1.0f - this.vcf_reso));
		this.vcf_e0 = (float) Math.exp(5.613f - 0.8f * this.vcf_envmod + 2.1553f * this.vcf_cutoff - 0.7696f * (1.0f - this.vcf_reso));
		this.vcf_e0 = this.vcf_e0 * (float) Math.PI / this.srate;
		this.vcf_e1 = this.vcf_e1 * (float) Math.PI / this.srate;
		this.vcf_e1 = this.vcf_e1 - this.vcf_e0;
		this.vcf_envpos = ENV_INC;
	}

	private float rct(float x) {
		if (x < 0f)
			return -0.5f;
		else
			return 0.5f;
	}

	private void noteOn(int note, boolean acc, boolean glide) {
		currentNote = note;

		this.vco_inc_src = this.vco_inc;
		this.vco_inc_dest = (440f / this.srate) * (float) Math.pow(2f, ((float) note - 57f) * (1f / 12f));

		this.cntmax = Math.round(this.srate * this.sld_spd);
		if (glide)
			this.cnt = 0;
		else
			this.cnt = this.cntmax - 1;

		this.vca_mode = 0;
		this.vcf_c0 = this.vcf_e1;

		this.vcf_envpos = ENV_INC;

		if (acc)
			this.vca_a0 = 0.5f + this.vca_accamt * 0.5f;
		else
			this.vca_a0 = 0.5f;

		this.IsNoteOn = true;
	}

	private void noteOff() {
		this.vca_a = 0;
		this.vca_mode = 2;
		this.IsNoteOn = false;
	}


	@Override
	protected void executeCommand(long timeStamp, String command) {
		// TODO Auto-generated method stub
		
	}

	@Override
	protected void computeBuffer() {
		try {
			float[][] arr = new float[][]{new float[buf.getSampleCount()]};
			processReplacing(arr,arr,buf.getSampleCount());
			for (int i = 0; i < buf.getChannelCount(); i++) {
				buf.setRawChannel(i, arr[0]);
			}
		} catch (Exception e) {
		}
	}


}

class DreiNullDreiProgram {
	public final static int PARAM_ID_VOLUME = 0;
	public final static int PARAM_ID_WAVEFORM = 1;
	public final static int PARAM_ID_GLIDE_SPEED = 2;
	public final static int PARAM_ID_ACC_AMOUNT = 3;
	public final static int PARAM_ID_CUT_OFF = 4;
	public final static int PARAM_ID_RESONANCE = 5;
	public final static int PARAM_ID_ENV_MOD = 6;
	public final static int PARAM_ID_ENV_DECAY = 7;

	public final static int NUM_PARAMS = PARAM_ID_ENV_DECAY + 1;

	private String name = "Init";

	private float volume = 1f;
	private float waveForm = 1;
	private float glideSpeed = 0.1f;
	private float accAmount = 0.5f;
	private float cutOff = 0.9f;
	private float reso = 0.1f;
	private float envMod = 1f;
	private float envDecay = 0.1f;

	public String getName() {
		return this.name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public float getVolume() {
		return this.volume;
	}

	public void setVolume(float v) {
		this.volume = v;
	}

	public float getWaveform() {
		return this.waveForm;
	}

	public void setWaveform(float v) {
		this.waveForm = v;
	}

	public float getGlideSpeed() {
		return this.glideSpeed;
	}

	public void setGlideSpeed(float v) {
		this.glideSpeed = v;
	}

	public float getAccAmount() {
		return this.accAmount;
	}

	public void setAccAmount(float v) {
		this.accAmount = v;
	}

	public float getCutoff() {
		return this.cutOff;
	}

	public void setCutoff(float v) {
		this.cutOff = v;
	}

	public float getResonance() {
		return this.reso;
	}

	public void setResonance(float v) {
		this.reso = v;
	}

	public float getEnvMod() {
		return this.envMod;
	}

	public void setEnvMod(float v) {
		this.envMod = v;
	}

	public float getEnvDecay() {
		return this.envDecay;
	}

	public void setEnvDecay(float v) {
		this.envDecay = v;
	}

}
